﻿using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows.Automation;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Compiled_Tests.AutomationAbstractions;
using LEET.CommonInterfaces.Automation;
using LEET.LEETCommon.WidgetLookup;
using LEET.Utilities;
using Compiled_Tests.Utils;

namespace LEET.LEETCommon
{
    public class Utilities
    {
        /// <summary>
        /// Max time for which LEET will wait for an element to appear.
        /// </summary>
        
        public const int SECOND = 1000;
        public const int MINUTE = 60 * SECOND;
        public const int MAX_ELEMENT_SEEK_TIME = 20 * SECOND;
        //public const string NCoverArgs = "//x C:\\XmlReport.ncoverage";
        public const int STD_DELAY = 250;

        private Hashtable PagedWidgets = new Hashtable();

        public AutomationElement getElementWithoutCache(string key, AutomationElement head)
        {
            int tries = 0;
            AutomationElement current = null;
            AutomationElement next = null;
            while (tries < 5000)
            {
                current = head;
                next = current;
                string[] hierarchy = key.Split(',');
                foreach (string s in hierarchy)
                {
                    next = current.FindFirst(TreeScope.Descendants, /*TreeScope.Children,*/
                        new PropertyCondition(AutomationElement.AutomationIdProperty, s.Trim()));
                    if (next == null)
                        next = current.FindFirst(TreeScope.Descendants, /*TreeScope.Children,*/
                            new PropertyCondition(AutomationElement.NameProperty, s.Trim()));
                    /*if (next == null)
                        next = current.FindFirst(TreeScope.Descendants, /*TreeScope.Children,/
                            new PropertyCondition(AutomationElement.ClassNameProperty, s.Trim()));*/
                    if (next == null)
                        break;
                    current = next;
                }
                ++tries;
                if (current != null)
                    tries = 6000;
            }
            return current;
        }

        public static IList<AE_Identifier> parseIdentifiers(String key)
        {
            IList<AE_Identifier> ids = new List<AE_Identifier>();
            String [] keys = key.Split(',');
            foreach (String tuple in keys)
            {
                Match m = Regex.Match(tuple, " *\\(\"(.*?)\"; \"(.*?)\"; \"(.*?)\"\\)");
                if (m.Success)
                {
                    AE_Identifier id = new AE_Identifier();
                    id.id = m.Groups[1].Value;
                    id.name = m.Groups[2].Value;
                    id.klass = m.Groups[3].Value;
                    ids.Add(id);
                }
                else throw new InvalidIdentifierException();
            }
            return ids;
        }

        public AutomationElement getElementWithoutCacheRecursive(string key, AutomationElement head)
        {
            // key is of form "(id; name; class), (id; name; class), ..."
            try
            {

                IUIPath path = UIPath.Apply(key);
                return getElementWithoutCacheRecursive(path, head);
            }
            catch (InvalidIdentifierException)
            {
                return getElementWithoutCache(key, head);
            }
        }

        public AutomationElement getElementWithoutCacheRecursive(IUIPath path, AutomationElement current)
        {
            if (path.head == null)
            {
                return current;
            }
            else
            {
                IUIPathElement pe = path.head;
                AutomationElementCollection next = null;
                //IList<AE_Identifier> dup = new List<AE_Identifier>();
                //for (int i = 1; i < ids.Count; i++)
                //    dup.Add(ids[i]);

                //AE_Identifier id = ids[0];

                Condition andCondition;

                List<Condition> conditions = new List<Condition>();
                conditions.Add(new PropertyCondition(AutomationElement.NameProperty, pe.Name));
                conditions.Add(new PropertyCondition(AutomationElement.AutomationIdProperty, pe.ID));
                conditions.Add(new PropertyCondition(AutomationElement.ClassNameProperty, pe.Class));
                while ( (next == null && conditions.Count > 0) || (next != null && next.Count <= 0) )
                {
                    try
                    {
                        if (conditions.Count > 1)
                            andCondition = new AndCondition(conditions.ToArray());
                        else
                            andCondition = conditions[0];
                        if ( current.Equals(null) )
                            throw new Exception();
                        next = current.FindAll(TreeScope.Children, andCondition);
                        conditions.RemoveAt(0);
                    }
                    catch(Exception e)
                    {
                        next = null;
                    }
                }

                if (conditions.Count <= 0)
                {
                    throw new FailedPathException();
                }

                for (int i = 0; i<next.Count; i++)
                {
                    try
                    {
                        AutomationElement possible = next[i];
                        return getElementWithoutCacheRecursive(path.tail, possible);
                    }
                    catch(Exception e)
                    {
                        if(i == (next.Count - 1))
                            throw e;
                    }
                }

                AutomationElementCollection aec = current.FindAll(TreeScope.Children, Condition.TrueCondition);
                StringBuilder buf = new StringBuilder();
                foreach (AutomationElement ae in aec)
                    buf.Append("\t(" + ae.Current.AutomationId + ", " + ae.Current.Name + ", " + ae.Current.ClassName + ")\r\n");

                throw new FailedPathException("Failed to find element: " + pe.ToString() + " possible alternatives: \r\n" + buf.ToString());
            }
        }

        /// --------------------------------------------------------------------
        /// <summary>
        /// Returns a reference to the AutomationElement specified by the given
        /// key starting from the head element.  If null or blank, returns head.
        /// If not found immediately, tries again a few times before returning 
        /// null (in case windows are loading or something).
        /// </summary>
        /// <param name="key">
        /// A string containing the type and id by which to search.
        /// </param>
        /// <param name="head">
        /// The top level UI element for the application under test.
        /// </param>
        /// <returns>
        /// The specified AutomationElement.
        /// </returns>
        /// --------------------------------------------------------------------
        public AutomationElement getElement(string key, AutomationElement head)
        {
            try
            {
                return new WidgetFinder().getElement(key, head);
            }
            catch (InvalidIdentifierException)
            {
                return getElementWithoutCache(key, head);
            }
        }

        public object getElement(string key, AutomationElement head, AutomationPattern type)
        {
            AutomationElement ae = getElementWithoutCache(key, head);
            ae.SetFocus();
            
            foreach (AutomationPattern pattern in ae.GetSupportedPatterns())
            {
                if (pattern.Equals(type))
                {
                    return ae.GetCurrentPattern(pattern);
                }
            }
            return null;
        }

        /// -------------------------------------------------------------------- 
        /// <summary>
        /// Starts up the GUI and returns a handle to the main window as an 
        /// AutomationElement.  See internal notes.
        /// </summary>
        /// <param name="path">
        /// The location of the .exe for the GUI to test.
        /// </param>
        /// <param name="args">
        /// Any arguments to be passed to the program on startup.
        /// </param>
        /// <param name="caller">
        /// Name of program under test.  Coverage results will be stored in a 
        /// folder of this name in the folder C:\Coverage.
        /// </param>
        /// <param name="coverage">
        /// Should code coverage be taken?  If yes, the SUT will be invoked 
        /// through NCover, and coverage data will be stored to the C:\Coverage 
        /// folder.
        /// </param>
        /// <returns>
        /// The main window, as an AutomationElement
        /// </returns>
        ///  --------------------------------------------------------------------
        public static Process Start_Process(string path, string args, Boolean coverage)
        {
            string PName = System.IO.Path.GetFileNameWithoutExtension(path);    //Assumes program name = executable name

            Process process = new Process();
            ProcessStartInfo info = new ProcessStartInfo(path);
            info.RedirectStandardError = true;
            info.UseShellExecute = false;
            //info.CreateNoWindow = true;

            process.StartInfo = info;
            string start_args =
                (args == null
                    ? ""
                    : args);
                
            
            process.StartInfo.Arguments = start_args;
            process.Start();

            return process;
        }

        private static void awaitProcessStart(Process process){
            
            IntPtr window = Helpers.Poll(delegate()
            {
                return process.MainWindowHandle;
            }, 100, MAX_ELEMENT_SEEK_TIME, IntPtr.Zero);
            
            if (window == IntPtr.Zero)
            {
                if (process != null) process.Kill();
                String message = "Main window of process did not start up. ";
                message += "\nAborting test.";
                if (process == null)
                    message += "\nProcess is null.";
                else
                    message += "\nProcess name = " + process.ProcessName;

                throw new Exception(message);
            }
            
        }

        private static void awaitUIInit(Process process)
        {
            //Waits for the window to initialize
            bool uiStarted = Helpers.Poll(delegate()
            {
                process.Refresh();
                return process.Responding;
            }, 100, MAX_ELEMENT_SEEK_TIME, false);

            if (!uiStarted)
                throw new Exception("UI failed to initialize (timeout: 10 seconds).");
            
            process.Refresh();
        }

        public static AutomationElement Get_MainAE(Process process)
        {

            awaitProcessStart(process);
            awaitUIInit(process);
            AutomationElement current = AutomationElement.FromHandle(process.MainWindowHandle);

            if (current == null)
                throw new Exception("Unable to acquire AutomationElement from process! \n(process may not be automatable)");

            return current;
        }

        /// <summary>
        /// If no args are supplied, it is assumed that there are none required.
        /// </summary>
        public static Process Start_Process(string path)
        {
            return Start_Process(path, null);
        }

        /// <summary>
        /// If no args are supplied, it is assumed that there are none required.
        /// </summary>
        public static Process Start_Process(string path, Boolean coverage)
        {
            return Start_Process(path, null, coverage);
        }

        /// <summary>
        /// If the coverage boolean is not specified, it is assumed false.
        /// CODE COVERAGE IS DISABLED BY DEFAULT
        /// </summary>
        public static Process Start_Process(string path, string args)
        {
            return Start_Process(path, args, false);
        }

        public static AutomationElement startup_1(string PName)
        {
            Process GUIProcess = null;
            Process[] pList;
            int runtime = 0;
            Boolean cont = true;
            while (cont)        //Feel free to fix this.
            {
                if ((pList = Process.GetProcessesByName(PName)).Length > 0)
                {
                    foreach (Process p in pList)
                    {
                        if (!(p.MainWindowHandle.Equals(IntPtr.Zero)))
                        {
                            GUIProcess = p;
                            cont = false;
                        }

                    }
                }
                if (runtime > MAX_ELEMENT_SEEK_TIME)
                {
                    foreach (Process proc in Process.GetProcessesByName(PName)) proc.Kill();
                    throw new Exception("UI process failed to start (timeout: 10 seconds).");
                }
                Thread.Sleep(100);
                runtime += 100;
            }

            return AutomationElement.FromHandle(GUIProcess.MainWindowHandle);
        }

        public static String Escape(String str)
        {
            return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "").Replace("\n", "");
        }
    }

    public struct AE_Identifier
    {
        public String id, name, klass;
    }

        
}
